#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <time.h>
#include <Cg/cg.h>
#include <Cg/cgD3D9.h>

#include "Entity3D.h"
#include "TestScene.h"
#include "PostScene.h"
#include "InputManager.h"
#include "LightScene.h"

//incluir el lib de Direct3d
#pragma comment (lib,"d3d9.lib")
#pragma comment (lib,"d3dx9.lib")

#pragma comment( lib, "cg.lib" )
#pragma comment( lib, "cgD3D9.lib" )

#define SCREEN_WIDTH 1024
#define SCREEN_HEIGHT 768

//declaraciones globales
LPDIRECT3D9 d3d;	//puntero a la interfaz d3d
LPDIRECT3DDEVICE9 d3ddev;	//puntero a la clase device

CUSTOMVERTEX OurVertices[] = {
								{-1.0f,1.0f,-1.0f,D3DXVECTOR3(0,1,0),0,1,0,1},
								{-1.0f,1.0f,-1.0f,D3DXVECTOR3(-1,0,0),0,1,0,1},
								{-1.0f,1.0f,-1.0f,D3DXVECTOR3(0,0,-1),0,1,0,1},
								{1.0f,1.0f,-1.0f,D3DXVECTOR3(0,1,0),0,1,0,1},
								{1.0f,1.0f,-1.0f,D3DXVECTOR3(1,0,0),0,1,0,1},
								{1.0f,1.0f,-1.0f,D3DXVECTOR3(0,0,-1),0,1,0,1},
								{-1.0f,-1.0f,-1.0f,D3DXVECTOR3(0,-1,0),0,1,0,1},
								{-1.0f,-1.0f,-1.0f,D3DXVECTOR3(-1,0,0),0,1,0,1},
								{-1.0f,-1.0f,-1.0f,D3DXVECTOR3(0,0,-1),0,1,0,1},
								{1.0f,-1.0f,-1.0f,D3DXVECTOR3(0,-1,0),0,1,0,1},
								{1.0f,-1.0f,-1.0f,D3DXVECTOR3(1,0,0),0,1,0,1},
								{1.0f,-1.0f,-1.0f,D3DXVECTOR3(0,0,-1),0,1,0,1},
								{-1.0f,1.0f,1.0f,D3DXVECTOR3(0,1,0),0,0,0,0},
								{-1.0f,1.0f,1.0f,D3DXVECTOR3(-1,0,0),0,0,0,0},
								{-1.0f,1.0f,1.0f,D3DXVECTOR3(0,0,1),0,0,0,0},
								{1.0f,1.0f,1.0f,D3DXVECTOR3(0,1,0),1,0,1,0},
								{1.0f,1.0f,1.0f,D3DXVECTOR3(1,0,0),1,0,1,0},
								{1.0f,1.0f,1.0f,D3DXVECTOR3(0,0,1),1,0,1,0},
								{-1.0f,-1.0f,1.0f,D3DXVECTOR3(0,-1,0),0,1,0,1},
								{-1.0f,-1.0f,1.0f,D3DXVECTOR3(-1,0,0),0,1,0,1},
								{-1.0f,-1.0f,1.0f,D3DXVECTOR3(0,0,1),0,1,0,1},
								{1.0f,-1.0f,1.0f,D3DXVECTOR3(0,-1,0),1,1,1,1},
								{1.0f,-1.0f,1.0f,D3DXVECTOR3(1,0,0),1,1,1,1},
								{1.0f,-1.0f,1.0f,D3DXVECTOR3(0,0,1),1,1,1,1},
							};
short indices[] = {
					2,5,8,
					8,5,11,
					13,1,19,
					19,1,7,
					23,17,20,
					20,17,14,
					10,3,22,
					22,4,16,
					12,15,0,
					0,15,3,
					9,21,6,
					6,21,18,
};

/*CUSTOMVERTEX plane[4]={
								{0.0f,0.0f,0.0f,D3DXVECTOR3(0,0,1)},
								{1.0f,0.0f,0.0f,D3DXVECTOR3(1,0,1)},
								{0.0f,1.0f,0.0f,D3DXVECTOR3(0,1,1)},
								{1.0f,1.0f,0.0f,D3DXVECTOR3(1,1,1)}};
short indicesPlane[6]=
							{
								0,1,2,
								2,1,3
							};*/
//////////////////////////////////Aumenatar el SceneManager////////////////////////////////////////////////////////////////////////////

////////////Vars Used for Shaders///////////////////////////
CGcontext context;
CGprogram program;
CGprogram programFragment;
CGprogram postFragment;
CGprogram postFragmentVetical;
CGprogram mergeProgram;
CGprogram lightSourceProgram;
CGprogram dispersionProgram;
CGparameter mvp;
CGparameter modelWorld;
CGparameter lightPos;
CGparameter cameraPos;
CGparameter normalTexture;
CGparameter renderTexture;
CGparameter renderTextureVertical;
CGparameter renderMergeTexture;
CGparameter modifiedMergeTexture;
CGparameter lightSourceTexture;
CGparameter skyBoxDispersionTexture;
CGparameter dispersionDecalTexture;

////////////////////////////////////////////////////////////////////////////
LPDIRECT3DTEXTURE9 pRenderTexture;
LPDIRECT3DSURFACE9 pRenderSurface;

LPDIRECT3DTEXTURE9 pRenderTextureVertical;
LPDIRECT3DSURFACE9 pRenderSurfaceVertical;

LPDIRECT3DTEXTURE9 pRenderTextureGlow;
LPDIRECT3DSURFACE9 pRenderSurfaceGlow;

LPDIRECT3DTEXTURE9 pSkyboxTexture;

IDirect3DSurface9* origTarget_;

///////////Vars Used for the Scene///////////////////
TestScene * testScene;
PostScene * postScene;
LightScene * lightScene;

InputManager * input;


//////////////////////////////////////////////////////


/////////////////////////////////////////////////////
//prototipos
void initD3D(HWND hWnd);
void render_frame(void);
void update_frame(void);
void createProgramShader(LPDIRECT3DDEVICE9 _d3ddev);

void cleanD3D(void);

//el prototipo de WindowProc
LRESULT CALLBACK WindowProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM IParam);

void initD3D(HWND hWnd)
{	
	d3d = Direct3DCreate9(D3D_SDK_VERSION);	//crear la interfaz
	
	D3DPRESENT_PARAMETERS d3dpp;	//estructura que guarda parametros del device

	ZeroMemory(&d3dpp,sizeof(d3dpp));	//limpiar struct
	d3dpp.Windowed = TRUE;	//ventana;
	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;	//descartar cuadros
	d3dpp.hDeviceWindow = hWnd;	//dar handle a la ventana a usar
	d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
	d3dpp.BackBufferWidth = SCREEN_WIDTH;
	d3dpp.BackBufferHeight = SCREEN_HEIGHT;
	d3dpp.EnableAutoDepthStencil = TRUE;
	d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

	//crear un device con la siguiente info
	d3d->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,hWnd,D3DCREATE_SOFTWARE_VERTEXPROCESSING,&d3dpp,&d3ddev);

	//d3ddev->SetRenderState(D3DRS_LIGHTING,TRUE);
	d3ddev->SetRenderState(D3DRS_ZENABLE,TRUE);
	//d3ddev->SetRenderState(D3DRS_AMBIENT,D3DCOLOR_XRGB(254,254,254));
	d3ddev->SetRenderState(D3DRS_CULLMODE,D3DCULL_NONE);
	//d3ddev->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);

	//setear para Point Sprites
	//d3ddev->SetRenderState(D3DRS_POINTSPRITEENABLE,TRUE);
	//d3ddev->SetRenderState(D3DRS_POINTSCALEENABLE,TRUE);

	//initScene
	testScene = new TestScene(d3ddev);
	postScene = new PostScene(d3ddev);
	lightScene = new LightScene(d3ddev);
	
	float fPointScaleB = 1;
	////////////////////////////////////////////////////////////////////////////////
	pRenderTexture = NULL;
	pRenderSurface = NULL;
	d3ddev->CreateTexture(512,
                                 512,
                                 1,
                                 D3DUSAGE_RENDERTARGET,
                                 D3DFMT_R5G6B5,
                                 D3DPOOL_DEFAULT,
                                 &pRenderTexture,
                                 NULL);
	//pRenderTexture->GetSurfaceLevel(0,&pRenderSurface);
	origTarget_ = NULL;
	d3ddev->GetRenderTarget(0,&origTarget_);
	
	D3DSURFACE_DESC desc;
	origTarget_->GetDesc( &desc );

	// create our surface as render target
	d3ddev->CreateRenderTarget(SCREEN_WIDTH,SCREEN_HEIGHT, D3DFMT_X8R8G8B8, 
									  desc.MultiSampleType, desc.MultiSampleQuality,
									  false, &pRenderSurface, NULL );
	pRenderTexture->GetSurfaceLevel(0,&pRenderSurface);


	/////////////////////////////////////////////////////////////////////////////////////////////////

	pRenderTextureVertical = NULL;
	pRenderSurfaceVertical = NULL;
	d3ddev->CreateTexture(512,
                                 512,
                                 1,
                                 D3DUSAGE_RENDERTARGET,
                                 D3DFMT_R5G6B5,
                                 D3DPOOL_DEFAULT,
                                 &pRenderTextureVertical,
                                 NULL);

	// create our surface as render target
	d3ddev->CreateRenderTarget(SCREEN_WIDTH,SCREEN_HEIGHT, D3DFMT_X8R8G8B8, 
									  desc.MultiSampleType, desc.MultiSampleQuality,
									  false, &pRenderSurfaceVertical, NULL );
	pRenderTextureVertical->GetSurfaceLevel(0,&pRenderSurfaceVertical);


	////////////////////////////////////////////////////////////////////////////////////////////////
	
	pRenderTextureGlow = NULL;
	pRenderSurfaceGlow = NULL;
	d3ddev->CreateTexture(512,
                                 512,
                                 1,
                                 D3DUSAGE_RENDERTARGET,
                                 D3DFMT_R5G6B5,
                                 D3DPOOL_DEFAULT,
                                 &pRenderTextureGlow,
                                 NULL);

	// create our surface as render target
	d3ddev->CreateRenderTarget(SCREEN_WIDTH,SCREEN_HEIGHT, D3DFMT_X8R8G8B8, 
									  desc.MultiSampleType, desc.MultiSampleQuality,
									  false, &pRenderSurfaceGlow, NULL );
	pRenderTextureGlow->GetSurfaceLevel(0,&pRenderSurfaceGlow);
	
	
	
	///////////////////////////////////////////////////////////////////////////////////////////////

	HRESULT hrTexture = D3DXCreateTextureFromFileEx(d3ddev,"skybox3.bmp",1536,256,0,D3DUSAGE_DYNAMIC,D3DFMT_FROM_FILE,D3DPOOL_DEFAULT,D3DX_DEFAULT,D3DX_DEFAULT,0,NULL,NULL,&pSkyboxTexture);
	
	//////////////////////////////////////////////////////////////////////////////////////////////
	//d3ddev->SetRenderState(D3DRS_POINTSCALE_B,*((DWORD*)&fPointScaleB));
	d3ddev->SetSamplerState(0,D3DSAMP_ADDRESSU,2);
	d3ddev->SetSamplerState(0,D3DSAMP_ADDRESSV,2);
	//createProgramShader(d3ddev);
}
void createProgramShader(LPDIRECT3DDEVICE9 _d3ddev)
{
	context = cgCreateContext();
	cgD3D9SetDevice(_d3ddev);
	cgSetParameterSettingMode(context, CG_DEFERRED_PARAMETER_SETTING);
	const char **profileOpts;
	CGerror error;
	CGprofile myCgVertexProfile;
	CGprofile myCgFragmentProfile;
  /* Determine the best profile once a device to be set. */
	myCgVertexProfile = cgD3D9GetLatestVertexProfile();
	error = cgGetError();

	profileOpts = cgD3D9GetOptimalOptions(myCgVertexProfile);
	error = cgGetError();
	const char  *myProgramName = "01_vertex_program",
                   *myVertexProgramFileName = "ej1.vp",
			      *myVertexProgramName = "myMain";
	
	program = cgCreateProgramFromFile(
      context,              /* Cg runtime context */
      CG_SOURCE,                /* Program in human-readable form */
      myVertexProgramFileName,  /* Name of file containing program */
      myCgVertexProfile,        /* Profile: OpenGL ARB vertex program */
      myVertexProgramName,      /* Entry function name */
      NULL);             /* Pass optimal compiler options */


	myCgFragmentProfile = cgD3D9GetLatestPixelProfile();
	profileOpts = cgD3D9GetOptimalOptions(myCgFragmentProfile);
	const char  *myFragmentProgramFileName = "ej1fragment.fp",
			      *myFragmentProgramName = "myMain";
	
	programFragment = cgCreateProgramFromFile(
      context,              /* Cg runtime context */
      CG_SOURCE,                /* Program in human-readable form */
      myFragmentProgramFileName,  /* Name of file containing program */
      myCgFragmentProfile,        /* Profile: OpenGL ARB vertex program */
      myFragmentProgramName,      /* Entry function name */
      NULL);             /* Pass optimal compiler options */
	
	const char  *myPostProgramFileName = "postFP.fp",
			      *myPostProgramName = "myMain";
	postFragment = cgCreateProgramFromFile(
      context,              
      CG_SOURCE,                
      myPostProgramFileName,  
      myCgFragmentProfile,        
      myPostProgramName,      
      NULL);  
	
	const char  *myPostVerticalProgramFileName = "postFPVertical.fp",
			      *myPostVerticalProgramName = "myMain";
	postFragmentVetical = cgCreateProgramFromFile(
      context,              
      CG_SOURCE,                
      myPostVerticalProgramFileName,  
      myCgFragmentProfile,        
      myPostVerticalProgramName,      
      NULL); 

	const char  *myMergeProgramFileName = "merge.fp",
			      *myMergeProgramName = "myMain";
	mergeProgram = cgCreateProgramFromFile(
      context,              
      CG_SOURCE,                
      myMergeProgramFileName,  
      myCgFragmentProfile,        
      myMergeProgramName,      
      NULL); 

	const char  *myLightSourceProgramFileName = "lightSource.fp",
			      *myLightSourceProgramName = "myMain";
	lightSourceProgram = cgCreateProgramFromFile(
      context,              
      CG_SOURCE,                
      myLightSourceProgramFileName,  
      myCgFragmentProfile,        
      myLightSourceProgramName,      
      NULL); 
	
	const char  *myDispersionProgramFileName = "dispersion.fp",
			      *myDispersionProgramName = "myMain";
	dispersionProgram = cgCreateProgramFromFile(
      context,              
      CG_SOURCE,                
      myDispersionProgramFileName,  
      myCgFragmentProfile,        
      myDispersionProgramName,      
      NULL); 

	error = cgGetError();
	const char* errorString = cgGetErrorString(error);
	mvp = cgGetNamedParameter(program,"modelViewProj");
	modelWorld = cgGetNamedParameter(program,"model");
	lightPos = cgGetNamedParameter(program,"lightPosition");
	cameraPos = cgGetNamedParameter(program,"cameraPosition");
	normalTexture = cgGetNamedParameter(programFragment,"normalTexture");
	renderTexture = cgGetNamedParameter(postFragment,"render");
	renderTextureVertical = cgGetNamedParameter(postFragmentVetical,"render");
	renderMergeTexture = cgGetNamedParameter(mergeProgram,"render");
	modifiedMergeTexture = cgGetNamedParameter(mergeProgram,"modified");
	lightSourceTexture = cgGetNamedParameter(lightSourceProgram,"texture");
	skyBoxDispersionTexture = cgGetNamedParameter(dispersionProgram,"skybox");
	dispersionDecalTexture = cgGetNamedParameter(dispersionProgram,"texture");
	error = cgGetError();
	const char* errorString2 = cgGetErrorString(error);
	cgD3D9LoadProgram(program,false,0);
	cgD3D9LoadProgram(programFragment,false,0);
	cgD3D9LoadProgram(postFragment,false,0);
	cgD3D9LoadProgram(postFragmentVetical,false,0);
	cgD3D9LoadProgram(mergeProgram,false,0);
	cgD3D9LoadProgram(lightSourceProgram,false,0);
	cgD3D9LoadProgram(dispersionProgram,false,0);
	error = cgGetError();
	const char* errorString1 = cgGetErrorString(error);
    cgD3D9BindProgram(program);
	
	/*cgD3D9BindProgram(programFragment);*/
	error = cgGetError();
}
void render_frame(void)
{
	//limpiar ventana 
	d3ddev->SetRenderState(D3DRS_NORMALIZENORMALS,TRUE);
	
	//d3ddev->SetRenderTarget(0,pRenderSurface);
	d3ddev->SetRenderTarget(0,origTarget_);
	
	
	//////////////////////////////////////////////////////////////////////////


	//////////////////////////////////////////////////////////////////////////
	
	d3ddev->Clear(0,NULL,D3DCLEAR_TARGET,D3DCOLOR_XRGB(0,0,0),1.0f,0);

	d3ddev->Clear(0,NULL,D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(0,0,0),1.0f,0);
	
	d3ddev->BeginScene();	//empezar escena (lockea memoria de video)
	
	//Matriz Transformacion de Proyeccion

	//Poner Dentro de la Scena///////////////////////////////////////////////////////////////
	D3DXMATRIX matProjection;
	D3DXMatrixPerspectiveFovLH(&matProjection,D3DXToRadian(60),(float)SCREEN_WIDTH/(float)SCREEN_HEIGHT, 1.0f,1000.0f);
	d3ddev->SetTransform(D3DTS_PROJECTION,&matProjection);
	
	//Dibujo de Escena
	//cgD3D9BindProgram(programFragment);
	cgD3D9BindProgram(dispersionProgram); 
	testScene->Draw(d3ddev);
	//cgD3D9UnbindProgram(programFragment);
	cgD3D9UnbindProgram(dispersionProgram);
	cgD3D9BindProgram(lightSourceProgram);
	lightScene->Draw(d3ddev);
	d3ddev->EndScene();
	cgD3D9UnbindProgram(lightSourceProgram);
	/*
	cgD3D9BindProgram(postFragment);
	d3ddev->SetRenderTarget(0,pRenderSurfaceVertical);
	
	
	d3ddev->Clear(0,NULL,D3DCLEAR_TARGET,D3DCOLOR_XRGB(0,0,0),1.0f,0);
	d3ddev->Clear(0,NULL,D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(0,0,0),1.0f,0);
	

	cgD3D9SetTexture(renderTexture,pRenderTexture);
	cgUpdateProgramParameters(postFragment);
	postScene->Draw(d3ddev);
	
	d3ddev->EndScene();
	cgD3D9UnbindProgram(postFragment);
	//////////////////////////////////////////////////////////////////////////////
	d3ddev->SetRenderTarget(0,pRenderSurfaceGlow);
	cgD3D9BindProgram(postFragmentVetical);
	//cgD3D9BindProgram(postFragmentVetical);

	d3ddev->Clear(0,NULL,D3DCLEAR_TARGET,D3DCOLOR_XRGB(0,0,0),1.0f,0);

	d3ddev->Clear(0,NULL,D3DCLEAR_TARGET,D3DCOLOR_XRGB(100,0,0),1.0f,0);

	d3ddev->Clear(0,NULL,D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(0,0,0),1.0f,0);

	CGerror error;
	cgD3D9SetTexture(renderTextureVertical,pRenderTextureVertical); 
	
	cgUpdateProgramParameters(postFragmentVetical);
	
	postScene->Draw(d3ddev);
	
	d3ddev->EndScene();
	cgD3D9UnbindProgram(postFragmentVetical);
	///////////////////////////////////////////////////////////////////////////////
	d3ddev->SetRenderTarget(0,origTarget_);
	cgD3D9BindProgram(mergeProgram);
	d3ddev->Clear(0,NULL,D3DCLEAR_TARGET,D3DCOLOR_XRGB(0,0,0),1.0f,0);
	d3ddev->Clear(0,NULL,D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(0,0,0),1.0f,0);

	cgD3D9SetTexture(renderMergeTexture,pRenderTexture); 
	cgD3D9SetTexture(modifiedMergeTexture,pRenderTextureGlow); 
	cgUpdateProgramParameters(mergeProgram);
	
	postScene->Draw(d3ddev);
	
	d3ddev->EndScene();
	cgD3D9UnbindProgram(mergeProgram);
	*/
	///////////////////////////////////////////////////////////////////////////////
	d3ddev->Present(NULL,NULL,NULL,NULL);	//muestra el frame creado
}
void update_frame(DWORD dt)
{
	input->Update(dt);
	testScene->Update(dt);
	lightScene->Update(dt);
	testScene->HandleInput(input);
	input->copyOldStates();
}
void cleanD3D(void)
{
	d3ddev->Release();
	d3d->Release();
	input->cleanDInput();
	delete testScene;
	delete postScene;
	cgD3D9SetDevice(0);
	cgDestroyProgram(program);
	cgDestroyContext(context);
}

LRESULT CALLBACK WindowProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM IParam)
{
	switch(message)
	{
		case WM_DESTROY:
			{
				PostQuitMessage(0);
				return 0;
			}
			break;
	}
	
	return DefWindowProc(hWnd,message,wParam,IParam);
}


int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR IpCmdLine,int nCmdShow)
{
	HWND hWnd;
	WNDCLASSEX wc;

	ZeroMemory(&wc,sizeof(WNDCLASSEX));

	wc.cbSize = sizeof(WNDCLASSEX);
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = WindowProc;
	wc.hInstance = hInstance;
	wc.hCursor = LoadCursor(NULL,IDC_ARROW);
	wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
	wc.lpszClassName = "WindowsClass";
	RegisterClassEx(&wc);
	hWnd = CreateWindowEx(NULL,"WindowsClass","Shaders Engine",WS_OVERLAPPEDWINDOW,10,10,SCREEN_WIDTH,SCREEN_HEIGHT,NULL,NULL,hInstance,NULL);
	ShowWindow(hWnd,nCmdShow);

	initD3D(hWnd);
	input = new InputManager();
	input->initDInput(hInstance,hWnd);
	MSG msg;
	
	createProgramShader(d3ddev);
	DWORD oldTickCount = GetTickCount();
	DWORD newTickCount = GetTickCount();
	while(TRUE)
	{
		newTickCount = GetTickCount();
		while(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		if(msg.message == WM_QUIT)
			break;
		update_frame(newTickCount-oldTickCount);
		render_frame();
		oldTickCount = newTickCount;
	}

	//clean up DirectX and COM

	cleanD3D();
	
	return msg.wParam;
}